macro full

Список используемых вспомогательных макросов

Name Category In Main Macro Doc Status
clickhouse__check_table_exists auxiliary full ready

Описание

Макрос full предназначен для объединения данных пайплайна с registry-таблицами. В зависимости от пайплайна поведение макроса меняется.

Применение

Имя dbt-модели (=имя файла в формате sql в папке models) должно соответствовать шаблону:
full_{название_пайплайна}.

Например, full_events.

Внутри этого файла вызывается макрос:

{{ datacraft.full() }}

Над вызовом макроса в файле будет указана зависимость данных через —depends_on. То есть целиком содержимое файла выглядит, например, вот так:

-- depends_on: {{ ref('graph_qid') }}

-- depends_on: {{ ref('link_events') }}

-- depends_on: {{ ref('link_registry_appprofilematching') }}

-- depends_on: {{ ref('link_registry_utmhashregistry') }}

{{ datacraft.full() }}

Аргументы

Этот макрос принимает следующие аргументы:

 

  1. params (по умолчанию: none)
  2. disable_incremental (по умолчанию: none)
  3. override_target_model_name (по умолчанию: none)
  4. date_from (по умолчанию: none)
  5. date_to (по умолчанию: none)
  6. limit0 (по умолчанию: none)
  7. metadata (по умолчанию: результат макроса project_metadata())

Функциональность

Глобально работу макроса можно разделить на несколько смысловых этапов:

  1. подготовка
  2. материализация
  3. отбор возможных и существующих таблиц registry
  4. создание основы запроса для каждого пайплайна
  5. отбор полей pipeline_columns для каждого пайплайна
  6. последовательное обогащение основного запроса данными из таблиц пайплайна registry

1) подготовка

Сначала макрос считает имя модели - либо из передаваемого аргумента (
override_target_model_name), либо из имени файла (this.name). При использовании аргумента override_target_model_name макрос работает так, как если бы находился в модели с именем, равным значению override_target_model_name.

Название модели, полученное тем или иным способом, разбивается на части по знаку нижнего подчёркивания. Например, название full_events разобьётся на 2 части, из этих частей макрос возьмёт в работу:

  • пайплайн - pipeline_name → events

2) материализация

Для каждого пайплайна в макросе задаётся своя материализация и своё поведение в начале, до присоединения registry-таблиц:

  • для пайплайна events- материализация table и соединение данных из таблиц link_events + graph_qid + имеющихся таблиц пайплайна registry

  • для пайплайна datestat - материализация incremental и соединение данных из таблицы link_datestat + имеющихся таблиц пайплайна registry

  • для пайплайна periodstat - материализация incremental и разбиение метрик по дням + добавление имеющихся таблиц пайплайна registry

3) отбор возможных и существующих таблиц пайплайна registry

На этом этапе сначала в макросе создаётся список возможных таблиц пайплайна registry - это нужно для всех пайплайнов. Для создания такого списка макрос обращается к metadata (она передаётся через аргумент и по умолчанию metadata = результат макроса project_metadata()).

Далее макрос при помощи вспомогательного макроса clickhouse__check_table_exists будет отбирать те таблицы пайплайна registry, которые в действительности существуют.

4) создание основы запроса для каждого пайплайна

Для каждого пайплайна создаём основу будущего SQL-запроса. Оформляем это при помощи common table expressions - CTE. Этот основной CTE для всех пайплайнов будет называться одинаково: t0.  К нему в дальнейшем будут добавляться t1, t2 и т.д. в зависимости от количества имеющихся таблиц пайплайна registry.

Для каждого пайплайна основа данных - своя. Поэтому макрос начинает перебор пайплайнов с помощью оператора if и каждому задаёт свою основу основу будущего запроса - t0:

  • для пайплайна events основа запроса это link_events + graph_qid:
WITH t0 AS (

SELECT * FROM {{ ref('link_events') }}

LEFT JOIN {{ ref('graph_qid') }} USING (__id, __link, __datetime)

)
  • для пайплайна datestat это link_datestat:
WITH t0 AS (

SELECT * FROM {{ ref('link_datestat') }}

)
  • для пайплайна periodstat поведение макроса более насыщенное: на этом этапе макрос берёт данные из link_periodstat и разбивает их по дням.

Чтобы осуществить это поведение, внутри макроса понадобится произвести дополнительные действия.

В макросе задаются наименования числовых типов данных:

{%- set numeric_types = ['UInt8', 'UInt16', 'UInt32', 'UInt64', 'UInt256',

                        'Int8', 'Int16', 'Int32', 'Int64', 'Int128', 'Int256',

                        'Float8', 'Float16','Float32', 'Float64','Float128', 'Float256','Num'] -%}

Затем задаются два списка - для колонок с числовыми и нечисловыми типами данных.

Далее макрос при помощи вспомогательного макроса get_columns_in_relation берёт все колонки, проверяет у каждой тип данных, и распределяет их по этим двум спискам.

Для этого пайплайна макрос не сразу создаёт t0, а сначала делает подготовительный шаг - unnest_dates.

Здесь макрос разбивает период на дни. Например, в данных была одна строка с такими значениями:

  • periodStart='2024-01-01'
  • periodEnd='2024-01-31'
  • cost =31000

Из этой одной строки макрос создаст 31 строку - по одной на каждый день этого периода и значением в новом столбце cost_per_day равным 1000. Вот как выглядит подготовительный шаг unnest_dates:

WITH unnest_dates AS (

SELECT *, {# берём все данные, какие были в таблице и добавляем к ним каждый день периода #}

    dateAdd(periodStart, arrayJoin(range( 0, 1 + toUInt16(date_diff('day', periodStart, periodEnd))))) AS period_date

    , COUNT(*) OVER(PARTITION BY

{% for c in columns_not_numeric -%}{{c}}

{% if not loop.last %},{% endif %}

{% endfor %}

    ) AS divide_by_days {# здесь мы вычисляем кол-во дней, на которое надо будет в дальнейшем делить метрики #}

FROM {{ ref('link_periodstat') }}

)

и после этого подготовительного шага уже создаётся t0. На этом шаге идёт отбор всех дат периода, нечисловые колонки идут в запрос SELECT в таком же виде, в каком они были изначально. А значения в числовых колонках делятся на количество дней в периоде. Таким образом для числовых данных образуются новые колонки, названия которых заканчиваются на _per_day. Например, была числовая колонка cost, а станет колонка cost_per_day. Вот как выглядит код этого шага:

, t0 AS (

SELECT period_date, {# отбираем все даты периода  #}

{% for column in columns_not_numeric -%}{{column}}, {# не числовые колонки - такими какими они и были #}

{% endfor %}   {# а значения в числовых колонках делим на количество дней в периоде #}

{% for column in columns_numeric -%}{{column}}/divide_by_days AS {{column}}_per_day {# и таким образом получаем новые столбцы #}

{% if not loop.last %},{% endif %}   {# например вместо cost будет cost_per_day #}

{% endfor %}

FROM unnest_dates

)

5) отбор полей pipeline_columns для каждого пайплайна

На этом шаге нет условия if, но, поскольку при вызове моделей у каждой свой pipeline_name, значения будут разными.

Макрос задаёт переменную pipeline_columns, в которую будет отбирать колонки с сущностями каждого пайплайна.

Далее на этом шаге макрос обращается к metadata, отбирает линки и получает все необходимые для дальнейшей работы данные по каждому нужному линку.

Ранее созданная переменная pipeline_columns заполняется теми сущностями (entities) из метадаты, которые соответствуют пайплайну модели и её линкам. Каждое название сущности дополняется окончанием Hash, и таким образом в этой переменной в процессе работы макроса оказывается уникальный список захэшированных названий колонок сущностей.

Например, для пайплайна events такой список (pipeline_columns) может выглядеть следующим образом:

'AccountHash', 'AppMetricaDeviceHash', 'MobileAdsIdHash', 'CrmUserHash', 'OsNameHash', 'CityHash', 'AdSourceHash', 'UtmParamsHash', 'UtmHashHash', 'TransactionHash', 'PromoCodeHash', 'AppSessionHash', 'VisitHash', 'YmClientHash'

6) последовательное обогащение основного запроса данными из таблиц пайплайна registry

На этом шаге в макросе происходит цикл for для последовательных джойнов: основа запроса - t0- последовательно обогащается данными из таблиц пайплайна registry (автоматически создаются и добавляются t1, t2).

Чтобы джойны могли отработать, в макросе создаётся переменная fields_list. В неё будут отбираться поля для будущего USING(...) в блоке JOIN.

Макрос перебирает таблицы в ранее отобранном списке существующих таблиц пайплайна registry (см. шаг 3). В названии этих registry-таблиц последняя часть названия - это линк.

Макрос обращается на этом этапе к metadata. И для каждого линка из названия registry-таблиц макрос отбирает нужную для дальнейшей работы информацию об этом линке из metadata.

Далее в переменную fields_list добавляются названия отобранных для задействованных линков сущностей (entities) . К названиям сущностей добавляются окончания Hash (так же, как это происходило для наполнения переменной pipeline_columns).

После этого макрос создаёт переменную existing_fields_list. В неё отбираются только те значения из fields_list, которые есть в pipeline_columns.

Таким образом в existing_fields_list попадают те названия полей, которые не просто относятся к пайплайну (то есть являются возможными вариантами для пайплайна), но и есть в реально существующих таблицах.

Далее макрос проходит циклом:

  • создаёт t1, который обогащает t0
  • создаёт t2, который обогащает t1 и тд.

Каждая таблица из t1, t2 и тд - это одна из таблиц пайплайна registry.

Для каждого оборота цикла в макросе автоматически подставляются и таблица registry, и её хэш-поля в USING(...) для верного джойна.

Джойн происходит до тех пор, пока у нас есть общие поля, по которым можно сделать USING(...).

Если общих полей для USING(...) нет, то мы этот шаг делаем без джойна, просто как SELECT * FROM предыдущий шаг.

После завершения цикла макрос обратится к последнему CTE и возьмёт все колонки кроме тех, которые не пойдут по условию с регулярным выражением. Регулярное выражение отсеет технически ненужные для дальнейшей работы колонки.

Если аргумент limit0 активирован, то в конце SQL-запроса будет добавлено LIMIT 0.

Пример

Файл в формате sql в папке models. Название файла full_events

Содержимое файла:

-- depends_on: {{ ref('graph_qid') }}

-- depends_on: {{ ref('link_events') }}

-- depends_on: {{ ref('link_registry_appprofilematching') }}

-- depends_on: {{ ref('link_registry_utmhashregistry') }}

{{ datacraft.full() }}

Примечания

Это восьмой из основных макросов.